package kulstats;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;

/**
 * This class handles the requests of getting courses from the datastore
 * @author Ruben
 *
 */
public class CourseManager {
	
	/**
	 *  Store the reference to the google datastore
	 */
	private static final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	/**
	 *  The coursecache, this holds the courses, which when the cache is empty are retrieved from the datastore
	 *  This cache ensures a lower datastore operation count
	 */
	private static final Map<String, Course> courseCache = new HashMap<String, Course>();
	
	
	/**
	 *  Public constructor
	 */
	public CourseManager() {
	}
	
	/**
	 * This method adds all the courses in the given list to the database
	 * @param list the course list to be added to the database
	 */
	public void addCourseList(List<Course> list)
	{
		for (int i = 0; i < list.size(); i++){
			Course courseO = list.get(i);
			// check if the id is already in use, to avoid doubles in database
			// uncached: can't create cache while the datastore is making it's list of courses, else the cache will be incomplete
			if (getCourseByIDuncached(courseO.getCourseID()) == null){
				Entity course = new Entity("Course");
				course.setProperty("Name", courseO.getName());
				course.setProperty("CourseID", courseO.getCourseID());
				course.setProperty("Professors", courseO.getProfessors());
				course.setProperty("SP", courseO.getStudypoints());
				course.setProperty("SuccesRate", courseO.getSuccessrate());
				course.setProperty("Year", courseO.getYear());
				datastore.put(course);
			}
		}
	}
	
	/**
	 * This method fills the datastore with all the courses
	 */
	public void fill()
	{
		List<Course> list = new ArrayList<Course>();
		list.add(new Course("Algemene en technische scheikunde", "bir01b1", null, 7, 22, 2011));
		list.add(new Course("Algemene natuurkunde", "bir01b2", null, 7, 22, 2011));
		list.add(new Course("Analyse I", "bir01b3", null, 6, 22, 2011));
		list.add(new Course("Analyse II", "bir01b4", null, 5, 22, 2011));
		list.add(new Course("Elektrische netwerken", "bir01b5", null, 3, 22, 2011));
		list.add(new Course("Inleiding tot de materiaalkunde", "bir01b6", null, 3, 22, 2011));
		list.add(new Course("Methodiek van de informatica", "bir01b7", null, 6, 22, 2011));
		list.add(new Course("P&O 1", "bir01b8", null, 4, 22, 2011));
		list.add(new Course("P&O 2", "bir01b9", null, 3, 22, 2011));
		list.add(new Course("Thermodynamica", "bir01b10", null, 3, 22, 2011));
		list.add(new Course("Toegepaste algebra", "bir01b11", null, 5, 22, 2011));
		list.add(new Course("Toegepaste mechanica, deel 1", "bir01b12", null, 5, 22, 2011));
		list.add(new Course("Wijsbegeerte", "bir01b13", null, 3, 22, 2011));
		
		list.add(new Course("Analyse III", "bir02b1", null, 3, 99, 2011));
		list.add(new Course("P&O 3", "bir02b2", null, 4, 88, 2011));
		list.add(new Course("Kansrekenen en statistiek", "bir02b3", null, 3, 77, 2011));
		list.add(new Course("Toegepaste Mechanica, deel 2", "bir02b4", null, 5, 66, 2011));
		list.add(new Course("Numerieke wiskunde", "bir02b5", null, 4, 55, 2011));
		list.add(new Course("Informatie-overdracht en -verwerking", "bir02b6", null, 5, 44, 2011));
		list.add(new Course("Organische scheikunde", "bir02b7", null, 3, 33, 2011));
		list.add(new Course("Economie", "bir02b8", null, 3, 22, 2011));

		list.add(new Course("Toegepaste discrete algebra", "bircw2b1", null, 3, 22, 2011));
		list.add(new Course("Gegevensbanken", "bircw2b2", null, 6, 22, 2011));
		list.add(new Course("Objectgericht programmeren", "bircw2b3", null, 6, 22, 2011));
		
		list.add(new Course("Atoomtheorie", "bircit2b1", null, 3, 22, 2011));
		list.add(new Course("Chemische Thermodynamica", "bircit2b2", null, 6, 22, 2011));
		list.add(new Course("Kunststoffen", "bircit2b3", null, 6, 22, 2011));
		
		list.add(new Course("Digitale elektronica en processoren", "biresat2b1", null, 6, 22, 2011));
		list.add(new Course("Systeemtheorie", "biresat2b2", null, 6, 22, 2011));
		list.add(new Course("Optische & Akoestische Golven", "biresat2b3", null, 3, 22, 2011));
		
		list.add(new Course("Probabilistisch ontwerpen", "birbwk2b1", null, 3, 22, 2011));
		list.add(new Course("Hydraulica", "birbwk2b2", null, 6, 22, 2011));
		list.add(new Course("Sterkteleer 2", "birbwk2b3", null, 3, 22, 2011));
		list.add(new Course("Constructiematerialen", "birbwk2b4", null, 3, 22, 2011));

		list.add(new Course("Geologie", "birgeo2b1", null, 6, 22, 2011));
		list.add(new Course("Systeemtheorie", "birgeo2b2", null, 3, 22, 2011));
		list.add(new Course("Elasticiteits- en plasticiteitsleer", "birgeo2b3", null, 6, 22, 2011));

		list.add(new Course("Structuurgenese van materialen", "birmat2b1", null, 6, 22, 2011));
		list.add(new Course("Mechanisch gedrag van materialen", "birmat2b2", null, 6, 22, 2011));
		list.add(new Course("Constructiematerialen", "birmat2b3", null, 3, 22, 2011));

		list.add(new Course("Toegepaste mechanica, deel 3", "birwerk2b1", null, 3, 22, 2011));
		list.add(new Course("Energieconversiemachines en -systemen", "birwerk2b2", null, 6, 22, 2011));
		list.add(new Course("Selectie en dimensionering", "birwerk2b3", null, 6, 22, 2011));
		addCourseList(list);
	}
	
	/**
	 * Convert an entity to a Course-object
	 * @param courseE the entity to be made to a course
	 * @return the Course-object that is made from the entity
	 */
	private Course entityToCourse(Entity courseE)
	{
		Map<String, Object> props = courseE.getProperties();
		Course course = new Course((String)props.get("Name"), (String)props.get("CourseID")
				, (List<Professor>)props.get("Professors"), ((Number)props.get("SP")).intValue()
				,((Number)props.get("SuccesRate")).intValue(), ((Number)props.get("Year")).intValue());
		return course;
	}
	
	/**
	 * Returns all the courses in the datastore
	 * @return an ArrayList with all the courses in the datastore, returns empty list when there are no courses in the datastore
	 */
	public ArrayList<Course> getAllCourses()
	{
		// check if cache is empty and fill if so
		fillCache();
		// else return value from cache
		ArrayList<Course> list = new ArrayList<Course>(courseCache.values());
		return list;
	}
	
	/**
	 * Returns the course with the given course ID
	 * @param CourseID the course ID to look for
	 * @return returns the course with the given ID, else if it can't find a course with that ID it returns null
	 */
	public Course getCourseByID(String CourseID)
	{
		// check if cache is empty and fill if so
		fillCache();
		// else return value from cache
		return courseCache.get(CourseID);
	}
	
	/**
	 * An uncached version of getCourseByID method, this will always search for a course with given ID in the datastore, instead of in the cache
	 * @param CourseID the course ID to look for
	 * @return returns the course with the given ID, else if it can't find a course with that ID it returns null
	 */
	private Course getCourseByIDuncached(String CourseID)
	{
		// filter on all courses which have CourseID as ID
		Query q = new Query("Course");
		q.addFilter("CourseID", Query.FilterOperator.EQUAL, CourseID);
		List<Entity> pq = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());
		// get the first element out of the list and return it, if possible
		Course c = null;
		if (!pq.isEmpty()){
			if(pq.get(0) != null){
				c = entityToCourse(pq.get(0));
			}
		}
		return c;
	}
		
	/**
	 * Fills the cache when it is empty
	 */
	private void fillCache()
	{
		if (courseCache.size() == 0){
			// retrieve all courses from datastore
			Query q = new Query("Course");
			List<Entity> pq = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());
			// add them to the cache
			for (int i = 0; i < pq.size(); i++){
				Course crs = entityToCourse(pq.get(i));
				courseCache.put(crs.getCourseID(), crs);
			}
		}
	}

}
